package gbench.appdemo.crawler;

import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import gbench.appdemo.crawler.CrawlerApplication.FileSrchEngine;
import gbench.appdemo.crawler.analyzer.lexer.Trie;
import gbench.appdemo.crawler.srch.AbstractJdbcSrchEngine.PageQuery;
import gbench.appdemo.crawler.srch.PageData;
import gbench.common.tree.LittleTree.IRecord;
import static gbench.common.tree.LittleTree.IRecord.*;
import static gbench.appdemo.crawler.srch.SrchUtils.*;
import static gbench.common.tree.LittleTree.*;

import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Stream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 以牡丹应用的模式(8081端口)进行启动 程序启动的使用请指定 jvm 参数选项为： -Dserver.port=8081
 * -Dserver.servlet.context-path=/poeny
 * 
 * @author gbench
 *
 */
@RequestMapping("/srch")
@RestController
@SpringBootApplication
public class CrawlerBootstrap extends AbstractState<CrawlerBootstrap>{

    /**
     * started callback
     * 
     * @return ApplicationRunner
     */
    @Bean
    ApplicationRunner onStarted() {
        return args -> {
            
            // 关键构造参数
            final var corpusHome = "D:\\sliced\\ebook\\中国笔记\\逻辑词汇";// 语料库的词汇目录
            final var indexedFileHome = "D:\\sliced\\tmp\\crawlerindexes";// 索引文件的路径
            final var snapHome = "D:\\sliced\\tmp\\crawlersnap";// 快照文件根目录
            
            // 成员属性创建与初始化
            this.crawlerApp = new CrawlerApplication(indexedFileHome, corpusHome,snapHome);
            this.srchEngine = this.crawlerApp.new FileSrchEngine();
            this.srchEngine.initialize();
            this.state.set(REC());// 清空state
            Trie.USE_RECURSIVE_GET_TRIE = false;// 使用LOOP模式的分词方式
        };
    }

    /**
     * 关键词以前缀来进行检索
     * 
     * @param prefix 检索关键字
     * @param size   最大长度
     * @return 检索关键字
     */
    @RequestMapping("keywords")
    public IRecord keywords(final String prefix, final Integer size) {
        synchronized(this.srchEngine) {
            if(this.srchEngine.getKeywords().size()<1) this.srchEngine.refresh();
        }
        // 直接返回结果
        return REC(
            "code", 0, // 错误代码
            "result", srchEngine.getKeywords()
                .stream() // 数据结果
                .filter(e -> e != null && e.startsWith((prefix == null ? "" : prefix).strip()))
                .limit(size == null ? 10 : size)// 默认长度
                .sorted((a, b) -> a.length() - b.length())
                .map(e -> REC(// 返回结果字段
                    "code", e, 
                    "label", e, 
                    "value", e))// getKeywords
        );// REC
    }

    /**
     * 全文检索
     * 
     * @param keyword 检索关键字
     * @return 检索关键字
     */
    @RequestMapping("lookup")
    public IRecord lookup(final String keyword) {
        return REC(
            "code", 0, // 错误代码
            "result", srchEngine.lookup(keyword == null ? "" : keyword, 50) // 数据结果
        );// REC
    }
    
    /**
     * 全文检索:待有检索条件的项目检索
     * 
     * @param line 检索关键字
     * @return 检索关键字
     */
    @RequestMapping("lookup2")
    public IRecord lookup2(final String line,HttpSession session,HttpServletRequest reqeuest,Integer size) {
        final var agentId = reqeuest.getHeader("agentId");// 客户id
        final var pageSize = size==null?10:size;// 页面大小
        final var ss = (line==null?" ":line).split("\\s+");
        final var rec = REC(
            "+symbol*",MFT("*{0}*",ss.length>0?ss[0]:"").trim(),
            "+file*",MFT("*{0}*",ss.length>1?ss[1]:"").trim()
        );// rec
        
        System.out.println(MFT("\n#lookup2:\nline:【{0}】\n rec:【{1}】,sessionId:{2},agentId:{3}",line,rec,session.getId(),agentId));
        final var query = rec.collect(bool_query_clc);
        PageQuery pageQuery = null;
        Optional<PageData> optional = null;
        
        // 设置各种key
        final var pageQueryKey = MFT("${0}.${1}.${2}",agentId,"lookup2","pageQuery");
        final var lineKey = MFT("${0}.${1}.${2}",agentId,"lookup2","line");
        final var pageNumKey = MFT("${0}.${1}.${2}",agentId,"lookup2","pagenum");
        final var pageTotalKey = MFT("${0}.${1}.${2}",agentId,"lookup2","pageTotal");
        
        if(!line.equals(this.stateOfT(lineKey, Object.class))) {// 初次访问
           
            pageQuery =  srchEngine.getPageQuery(query);
            pageQuery.initialize(10);
            optional = pageQuery.getDataNoThrow();
            
            //程序状态
            this.setState(line,lineKey);// 记录当前检索的请求
            this.setState(pageQuery,pageQueryKey);
            this.setState(1l,pageNumKey);// 记录行号
            this.setState((long)Math.ceil(pageQuery.getTotalHits().value/pageSize)+1,pageTotalKey);
        } else { // 二次访问
            pageQuery =  this.stateOfT(pageQueryKey,PageQuery.class);
            optional = pageQuery.nextPageNoThrow();
            
            final var pagenum = this.getState(1l,pageNumKey);
            final var pageTotal = this.getState(1l,pageTotalKey);
        
            // 更新程序状态
            this.setState(optional.isPresent()?pagenum+1:pageTotal,pageNumKey);// 设置当前页
        }// if
        
        return REC(
            "code", 0, // 错误代码
            "pagenum",this.stateOfInteger(pageNumKey),
            "size",optional.map(e->e.getTotalHits().value).orElse(0l),
            "total",this.stateOfInteger(pageTotalKey),
            "result", optional.map(e->e.hitsStream()).orElse(Stream.of()) // 数据结果
        );// REC
    }
    
    /**
     * 刷新关键词列表
     * 请求示例
     * http://localhost:8081/poeny/srch/refresh
     * @return 刷新列表
     */
    @RequestMapping("refresh")
    public IRecord refresh() {
        es.execute(()->this.srchEngine.refresh());// 执行刷新请求
        return REC(
            "code", 0, // 错误代码
            "result", "刷新请求已经收到" // 数据结果
        );// REC
    }

    /**
     * 全文检索
     * 
     * 请求示例
     * http://localhost:8081/poeny/srch/indexfiles?homeFile=C:/Users/xuqinghua/Desktop/史记.txt
     * @param homeFile 索引文件的起始位置
     * @return 检索关键字
     */
    @RequestMapping("indexfiles") 
    @SuppressWarnings("unchecked")
    public IRecord indexFiles(final String homeFile) {
        
        if(homeFile==null) {
            return REC(
                "code", 2, // 错误代码
                "result", MessageFormat.format("fileToBeIndexed 为空,不予执行索引",""));// REC
        }
        
        // 索引线程
        final Runnable runnable = ()->{// 创建线程 索引文件
            this.crawlerApp.indexFiles(homeFile, rec ->{ // 索引文件
                final var tokens = rec.get("tokens",e->(Stream<IRecord>)e);
                final var files = rec.get("files",e->(Stream<File>)e);
                final var decomposeTime = rec.lng("decompose_time");
                final var start = this.stateOfLong("indexfiles.start.time");
                System.out.println(MessageFormat.format("\n## {0} 索引完毕 !\n 索引词条:{1} 条 ,索引文件:{2} 个, 分词历时:{3} s , 索引总历时:{4} s ", 
                    this.stateOfString("indexfiles.file.to.be.indexed"),
                    tokens.count(),
                    files.count(),
                    decomposeTime/1000.0, // 分解结构
                    (System.currentTimeMillis()-start)/1000.0 // 历时时间
                ));// println
                
                this.state("indexfiles.running", false);// 清除运行状态
                this.state("indexfiles.file.to.be.indexed",null );// 去除 索引文件
                this.state("indexfiles.start.time",null);// 去除开始时间
            });// 索引文件
        };
        
        // 仅当没有文件被索引的时候，开启文件索引功能
        if(!this.stateOfBoolean("indexfiles.running")) {// 读取indexfiles的执行状态
            this.state("indexfiles.running", true);// true 表示开始执行
            this.state("indexfiles.file.to.be.indexed", homeFile);// true 表示开始执行
            this.state("indexfiles.start.time",System.currentTimeMillis());// 记录开始时间
            
            es.execute(runnable);// 开始执行文件索引
            
            // 返回结果
            return REC(
                "code", 0, // 错误代码
                "result", MessageFormat.format("开始索引:{0}！", homeFile));// REC
        } else {
            // 返回结果
            return REC(
                "code", 1, // 错误代码
                "result", MessageFormat.format("{0}正在被索引， 请稍后再予以申请！", this.stateOfString("indexfiles.file.to.be.indexed")));// REC
        }// 线程文件
    }
    
    
   
    private FileSrchEngine srchEngine;// 搜索引擎
    private CrawlerApplication crawlerApp;// 搜索APP
    private ExecutorService es = Executors.newFixedThreadPool(1);// 创建一个线程队列
    
    /**
     * 启动函数
     * 
     * @param args
     * @throws IOException 
     */
    public static void main(String[] args) {
        SpringApplication.run(CrawlerBootstrap.class, args);
    }
}
